use std::{collections::HashMap, sync::Arc};

use anyhow::Error;
use reqwest::Url;
use serde_json::{Map, Value};
use talktive::{
    HistoryEncoder, TalktiveKenerl, TalktiveKenerlAns, ToolParm, ToolParmVal, ToolSig,
    ToolSigValueizer,
};

use crate::{new_client, ChatGLMRemoteConfig, GenerateConfig};

pub struct ChatGLMRemote {
    client: reqwest::Client,
    addr: String,
    gen_config: GenerateConfig,
}

impl TalktiveKenerl for ChatGLMRemote {
    type KenerlConfig = ChatGLMRemoteConfig;
    type ToolSigValueizer = ChatGLMRemote;
    type HistoryEncoder = ChatGLMRemote;
    fn build(config: &Self::KenerlConfig) -> anyhow::Result<Self>
    where
        Self: Sized,
    {
        Ok(Self {
            client: new_client(&config.app_key, config.exp_dur)?,
            addr: config.addr.clone(),
            gen_config: config.gen_config.clone(),
        })
    }
    async fn send(
        &self,
        history: Option<Value>,
        tools: Option<Arc<Value>>,
    ) -> anyhow::Result<Vec<TalktiveKenerlAns>> {
        let encoded = Self::encode(history, tools, &self.gen_config);
        let request = self
            .client
            .post(&self.addr)
            .body(serde_json::to_string(&encoded)?)
            .send()
            .await?
            .text()
            .await?;
        let val: Value = serde_json::from_str(&request)?;
        if let Some(choices) = val["choices"].as_array() {
            let ans = choices
                .iter()
                .map(|v| Self::split_choice(v))
                .collect::<Vec<_>>();
            return Ok(ans);
        }
        Err(Error::msg("Failed to jsonize result."))
    }
    async fn stop(&self) -> anyhow::Result<()> {
        Ok(())
    }

    fn name() -> &'static str {
        "ChatGLMRemote"
    }
}

impl ChatGLMRemote {
    fn encode(history: Option<Value>, tools: Option<Arc<Value>>, config: &GenerateConfig) -> Value {
        let mut val = Map::new();
        val.insert("model".into(), Value::String("glm-4".into()));
        val.insert("max_tokens".into(), Value::from(config.max_tokens));
        val.insert("temperature".into(), Value::from(config.temperature));
        val.insert("top_p".into(), Value::from(config.top_p));
        val.insert("seed".into(), Value::from(config.seed));
        val.insert("messages".into(), history.unwrap_or(Value::Null).clone());
        val.insert("stream".into(), Value::Bool(false));
        val.insert("tools".into(), {
            match tools {
                Some(tools) => tools.as_ref().clone(),
                None => Value::Null,
            }
        });
        val.insert("tool_choice".into(), Value::String("auto".into()));
        Value::Object(val)
    }
    fn split_choice(val: &Value) -> TalktiveKenerlAns {
        match val["finish_reason"].as_str() {
            Some("tool_calls") => {
                let calls = val["message"]["tool_calls"]
                    .as_array()
                    .unwrap()
                    .iter()
                    .map(|t| Self::split_toolcall(t))
                    .collect::<Vec<_>>();
                TalktiveKenerlAns::ToolCall(calls)
            }
            _ => {
                let content = val["message"]["content"].as_str().unwrap_or("").to_owned();
                let role = val["message"]["role"].as_str().unwrap_or("");
                TalktiveKenerlAns::Message(talktive::Message::from_str_parts(role, content))
            }
        }
    }
    fn split_toolcall(val: &Value) -> ToolParm {
        let func = &val["function"];
        let name = func["name"].as_str().unwrap().to_owned();
        let arguments: Value = serde_json::from_str(func["arguments"].as_str().unwrap()).unwrap();
        let arguments = arguments.as_object().unwrap().to_owned();
        let arguments = arguments
            .into_iter()
            .map(|(name, val)| (name, ToolParmVal::from_value(val)))
            .collect::<HashMap<String, ToolParmVal>>();
        ToolParm::new(name, arguments)
    }
}
impl HistoryEncoder for ChatGLMRemote {
    fn encode_single(message: talktive::Message) -> Value {
        let mut val = Map::new();
        val.insert("role".into(), Value::String(message.role_name().into()));
        val.insert("content".into(), Value::String(message.content().into()));
        Value::Object(val)
    }
}
impl ToolSigValueizer for ChatGLMRemote {
    fn toolsig_valueize_single(tool: &ToolSig) -> Value {
        let mut val = Map::new();
        let mut func = Map::new();
        let mut parameters = Map::new();
        let mut properties = Map::new();
        let mut required_list = Vec::new();
        val.insert("type".into(), Value::String("function".into()));
        func.insert("name".into(), Value::String(tool.name.clone()));
        func.insert(
            "description".into(),
            Value::String(tool.description.clone()),
        );
        parameters.insert("type".into(), Value::String("object".into()));
        for parameter in &tool.parameters {
            let mut propertiy = Map::new();
            propertiy.insert(
                "type".into(),
                Value::String(parameter.parameter_type.as_str().to_owned()),
            );
            propertiy.insert(
                "description".into(),
                Value::String(parameter.description.clone()),
            );
            let propertiy = Value::Object(propertiy);
            properties.insert(parameter.name.clone(), propertiy);
            if parameter.required {
                required_list.push(Value::String(parameter.name.clone()));
            }
        }
        let properties = Value::Object(properties);
        let required_list = Value::Array(required_list);
        parameters.insert("properties".into(), properties);
        parameters.insert("required".into(), required_list);
        let parameters = Value::Object(parameters);
        func.insert("parameters".into(), parameters);
        let func = Value::Object(func);
        val.insert("function".into(), func);
        Value::Object(val)
    }
}
